Class对象(Java)
关于Class对象,需要了解的是:
- 每个类都有一个Class对象
- 默认情况下,类通过“原生类加载器”从磁盘中加载;可以挂接其他的类加载器
- Java采用动态加载机制;类在第一次被访问静态成员时被加载,从这个角度上讲,可以认为构造器为类的静态方法
获得Class对象
获得Class对象的方法有:
- Class类的静态方法
forName
,Class.forName("full-qualified-class-name")
- 对象的
getClass()
方法,该方法属于Object
- 类字面常量
class
,ClassName.class
- 对于基本类型,有
boolean.class <=> Boolean.TYPE
,但boolean.class != Boolean.class
- 对于基本类型,有
以上方法获得的Class对象是等价的,即
String s = "string";
Class stringClass1 = s.getClass();
Class stringClass2 = Class.forName("java.lang.String");
Class stringClass3 = String.class;
stringClass1 == stringClass2; // =true
stringClass1 == stringClass3; // =true
Class对象的几个方法
getFields()
/getDeclaredFields()
getFields()
获得类的所有public
数据成员,包括父类的;getDeclaredFields()
获得类的所有数据成员(public
,protected
,private
,default
),不包括父类。其他
getXxx()
/getDeclaredXxx()
逻辑与此类似。newInstance()
创建实例,返回
Object
类型。
泛化的Class引用
Class引用支持泛型引用(Java SE5+),如Class<Integer> intClass;
。
newInstance()
返回确定类型不使用泛型引用,
newInstance()
方法返回Object
类型对象;使用后可返回确定类型,如Class<Integer> intClass = Integer.class; Class normalClass = Integer.class; Integer intValue = intClass.newInstance(); Object normalValue = normalClass.newInstance();
?
通配符?
通配符可解决具有继承关系的Class引用,如下例所示,norNumClass
不能引用int.class
,相反,numClass
却可以。Class<?> normalClass; normalClass = String.class; normalClass = HashMap.class; Class<? extends Number> numClass; numClass = int.class; numClass = Number.class; Class<Number> norNumClass = Number.class;
getSupperclass()
返回父类泛化类型虽然,可以在编译时确定一个类的父类,但使用
getSupperclass()
获得父类Class引用时,只能使用下面的方式:Class<? super String> stringSuper = String.class.getSupperclass();
特别地,
- 对于Object/接口/基本类型/void,返回
null
- 对于Object,引用方式需改为
Class<Object> objectSuper = Object.class.getSupperclass();
- 对于Object/接口/基本类型/void,返回
初始化(深入研究)
之前曾经讨论过创建对象时的初始化顺序,这里将进一步研究。
类的初始化
类的加载和初始化顺序为:
加载
由类加载器执行,从磁盘(或其他)中查找字节码,创建Class对象。
链接
验证字节码,为静态域分配存储空间。该阶段将初始化编译时常量(static final修饰,且值为常量)。如果必须的话,解析对其他类的引用。
初始化
先初始化父类静态变量和初始化块,再初始化本类的静态变量和初始化块。
对于类的初始化,有以下结论:
- 引用类的字面常量
class
或编译时常量,仅执行加载和链接,不初始化 - 引用
Class.forName()
或类的静态方法或非常数静态域时,先执行加载和链接,再执行初始化
对象的初始化
创建对象时,首先执行类的初始化,即类的加载、链接和初始化。之后,执行对象的初始化,包括:
- 在堆上为对象分配存储空间
- 将存储空间清零,基本类型数据设为默认值,引用类型设为null
- 执行非静态对象(包括非静态域和初始化块)初始化
- 执行构造器
当存在继承关系时,1、2步将同时完成父类和子类的存储空间分配和清零,之后先执行父类的非静态对象初始化和构造器,再执行子类的非静态对象初始化和构造器
示例(类的初始化)
逐句执行ClassInitialization.main()
中的每一条语句,观察相应的输出,来理解类的初始化。
public class BaseClass {
public static final String aaToExtend = "base aa";
public static final String aa = "aa";
public static String bb = "bb";
static {
System.out.println("base static init");
}
}
public class ExtendClass extends BaseClass {
public static final String aaToExtend = "extend aa";
public static final String cc = "cc";
public static String dd = "dd";
static {
System.out.println("extend static init");
}
}
public class ClassInitialization {
public static void main(String[] arg) throws ClassNotFoundException {
// System.out.println(BaseClass.aaToExtend); //base aa
// System.out.println(BaseClass.aa); //aa
// System.out.println(BaseClass.bb); //base static init\nbb
// Class<BaseClass> c = BaseClass.class; //
// Class c = Class.forName("BaseClass"); //base static init
// System.out.println(ExtendClass.aaToExtend); //extend aa
// System.out.println(ExtendClass.cc); //cc
// System.out.println(ExtendClass.dd); //base static init\nextend static init\ndd
// Class<ExtendClass> c = ExtendClass.class; //
// Class c = Class.forName("ExtendClass"); //base static init\nextend static init
}
}